# !/usr/bin/env python3
# coding=utf8
"""
回测脚本, 无界面,
"""
import argparse
import datetime
import logging
import pathlib
from typing import Any, Dict, List, Set, Tuple, Type, Optional, Union, Callable
from vnpy.tools.chinese_mainland_business_day import ChineseMainlandBusinessDay
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.optimize import OptimizationSetting
from vnpy_ctabacktester.ui.widget import OptimizationSettingEditor
from vnpy_ctastrategy.backtesting import BacktestingEngine, BacktestingMode
from vnpy_zxtools.ctabacktester.cta_config import CtaConfigLite, CtaConfig


def initialize_logging():
    """"""
    c_handler: logging.StreamHandler = logging.StreamHandler()
    f_handler: logging.FileHandler = logging.FileHandler(filename=f"{__file__}.log", mode="a", encoding="utf8")
    logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(message)s", handlers=[c_handler, f_handler])


def logging_log(msg) -> None:
    """"""
    logging.info(msg)


def run_bf_optimization(cta_setting: CtaConfig, f_ga_t_bf: bool = False):
    """
    多进程优化, 基于 vnpy/examples/cta_backtesting/backtesting_demo.ipynb 进行修改,
    遗传算法(Genetic Algorithm, GA), 蛮力优化(brutal force optimization),
    """
    assert cta_setting.target_name in OptimizationSettingEditor.DISPLAY_NAME_MAP.values()

    engine: BacktestingEngine = BacktestingEngine()

    engine.output = logging_log

    engine.set_parameters(
        vt_symbol=cta_setting.vt_symbol,
        interval=cta_setting.interval,
        start=cta_setting.start,
        rate=cta_setting.rate,
        slippage=cta_setting.slippage,
        size=cta_setting.size,
        pricetick=cta_setting.pricetick,
        capital=cta_setting.capital,
        end=cta_setting.end,
        mode=cta_setting.mode,
        risk_free=cta_setting.risk_free,
        annual_days=cta_setting.annual_days,
        half_life=cta_setting.half_life,
        do_continue=cta_setting.do_continue,
    )

    engine.add_strategy(cta_setting.strategy_class, {})

    optimization_setting: OptimizationSetting = OptimizationSetting()

    if isinstance(cta_setting.backtest_resume, int) and 0 < cta_setting.backtest_resume:
        optimization_setting.backtest_resume = cta_setting.backtest_resume

    if isinstance(cta_setting.abandon_results, bool) and cta_setting.abandon_results:
        optimization_setting.abandon_results = cta_setting.abandon_results

    for name, d in cta_setting.parameters.items():
        start, last, step = d["start"], d["last"], d["step"]
        if start == last:
            step = last = None
        optimization_setting.add_parameter(name=name, start=start, end=last, step=step)

    if cta_setting.strategy_class.get_class_parameters().get(Interval.__name__.lower(), None) in Interval._value2member_map_:
        # 本来应该在 cta_setting.parameters 里面配置 Interval.__name__.lower() 的, 但是已经有 cta_setting.interval 了,
        # 为了避免重复配置, 所以在这里自动猜测(参数里面是否有 interval), 如果有, 就自动取用 cta_setting.interval 的值,
        optimization_setting.add_parameter(name=Interval.__name__.lower(), start=cta_setting.interval.value, end=None, step=None)

    optimization_setting.set_target(cta_setting.target_name)

    if f_ga_t_bf:  # True => bf
        engine.run_bf_optimization(optimization_setting=optimization_setting, output=True, max_workers=None)
    else:
        raise NotImplementedError


if __name__ == "__main__":
    initialize_logging()

    logging.info(f"++++++++++ START ++++++++++")

    parser = argparse.ArgumentParser()
    parser.add_argument("--file", "-f", type=str, default="", help="json配置文件")
    args = parser.parse_args()

    json_file: pathlib.Path = pathlib.Path(args.file)

    if not json_file.is_file():
        logging.info(f"json_file={json_file.absolute()}, not is_file, will exit...")
        exit(1)

    logging.info(f"json_file={json_file.absolute()},")

    cta_setting_lite: CtaConfigLite = CtaConfigLite.load_from_file(filepath=json_file)
    logging.info(cta_setting_lite)

    cta_setting: CtaConfig = cta_setting_lite.to_cta_config()
    logging.info(cta_setting)

    assert (0 <= cta_setting.start.weekday() <= 4) and (0 <= cta_setting.end.weekday() <= 4)

    # TODO: 理论上分割的时间点应该是"周五下班时刻", 因为周五21点之后有一个交易时间段是周一的夜盘,
    days: List[datetime.date] = ChineseMainlandBusinessDay.calculate_business_day_s(
        begin=cta_setting.start.date(),
        last=cta_setting.end.date(),
    )

    logging.info(f"len_days={len(days)}, start={cta_setting.start}, end={cta_setting.end},")
    if not (5 * 8 <= len(days) < 5 * 9):  # 拍脑袋确定至少回测2个月(8周, 8*5=40)
        logging.info(f"数量异常, 本次回测{len(days)}天, start={cta_setting.start}, end={cta_setting.end},")
        # exit(1)

    run_bf_optimization(cta_setting=cta_setting, f_ga_t_bf=True)
